﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

using ICSharpCode.SharpZipLib.Zip;
using MyLibrary.Security.CRC;

namespace MyLibrary.IO
{
    public class Zipper : IDisposable
    {
        #region 事件和代理
        /// <summary>
        /// 解压和压缩文件处理过程的代理
        /// </summary>
        /// <param name="e">处理过程事件</param>
        public delegate void ZipProcessHandler(ZipProcessEventArgs e);
        /// <summary>
        /// 当在进行解压和压缩文件时触发
        /// </summary>
        public event ZipProcessHandler OnZipProcess;
        public event ZipProcessHandler OnUnzipProcess;
        #endregion

        #region 构造函数
        public Zipper()
        {
        }
        ~Zipper()
        {
            disposing(false);
        }
        #endregion

        #region 类属性
        private int _zipLevel = 6;
        /// <summary>
        /// 压缩级别[0~9](默认为6)
        /// </summary>
        public int ZipLevel
        {
            get { return _zipLevel; }
            set 
            {
                if (value > 9 || value < 0) throw new ArgumentOutOfRangeException("压缩级别范围为0~9");
                _zipLevel = value;
            }
        }

        private string _foldertozip = String.Empty;
        /// <summary>
        /// 需要被压缩的文件夹
        /// </summary>
        /// <example>C:\ZipFolder</example>
        public string FolderTozip
        {
            get { return _foldertozip; }
            set { _foldertozip = value; }
        }
        private List<string> _filestozip = null;
        /// <summary>
        /// 需要被压缩的文件列表
        /// </summary>
        public List<string> FilesTozip
        {
            get { return _filestozip; }
            set { _filestozip = value; }
        }
        private string _zipfilename = String.Empty;
        /// <summary>
        /// 压缩文件名
        /// </summary>
        /// <example>C:\ZipFile.zip</example>
        public string ZipFileName
        {
            get { return _zipfilename; }
            set { _zipfilename = value; }
        }

        private string _unzipfolder = String.Empty;
        /// <summary>
        /// 解压文件夹
        /// </summary>
        public string UnzipFolder
        {
            get { return _unzipfolder; }
            set { _unzipfolder = value; }
        }
        #endregion

        private ZipOutputStream _zipStream = null;
        private ZipInputStream _zipInputStream = null;
        private Crc32 _crc = null;

        #region 压缩文件夹
        /// <summary>
        /// 压缩文件夹,包括子目录文件
        /// </summary>
        /// <param name="foldertozip">文件夹名</param>
        /// <param name="zipfilename">压缩后的文件名</param>
        /// <param name="password">密码</param>
        public void ZipFolder(string foldertozip, string zipfilename, string password)
        {
            if (String.IsNullOrEmpty(foldertozip)) throw new ArgumentNullException("foldertozip");
            if (String.IsNullOrEmpty(zipfilename)) throw new ArgumentNullException("zipfilename");
            if (!Directory.Exists(foldertozip)) throw new DirectoryNotFoundException("指定的文件夹未能找到");
            List<string> fileList = new List<string>();
            //递归获取指定目录下所有文件
            GetAllFile(foldertozip, ref fileList);
            if (fileList.Count == 0) throw new ArgumentException("指定的目录下不存在文件");
            //建立事件参数
            ZipProcessEventArgs e = new ZipProcessEventArgs();
            if (OnZipProcess != null)
            {
                e.FileTotal = (double)fileList.Count;
                e.CurrentCount = 0.0;
            }

            //建立ZIP文件
            CreateZipFile(zipfilename, _zipLevel, password);
            foreach (string file in fileList)
            {
                ZipSingleFile(file);
                if (OnZipProcess != null)
                {
                    e.CurrentCount++;
                    e.CurrentFileName = file;
                    //触发事件
                    this.OnZipProcess(e);
                }
            }
            CloseZipFile();
        }
        /// <summary>
        /// 压缩文件夹,包括子目录文件
        /// </summary>
        public void ZipFolder()
        {
            ZipFolder(this.FolderTozip, this.ZipFileName);
        }

        /// <summary>
        /// 压缩文件夹,包括子目录文件
        /// </summary>
        /// <param name="foldertozip">文件夹名</param>
        /// <param name="zipfilename">压缩后的文件名</param>
        public void ZipFolder(string foldertozip, string zipfilename)
        {
            ZipFolder(foldertozip, zipfilename, String.Empty);
        }
        #endregion

        #region 压缩指定文件
        /// <summary>
        /// 压缩指定的文件(组)
        /// </summary>
        /// <param name="filestozip">要压缩的文件组</param>
        /// <param name="zipfilename">压缩后的文件名</param>
        /// <param name="password">密码</param>
        public void ZipFiles(List<string> filestozip, string zipfilename, string password)
        {
            if (String.IsNullOrEmpty(zipfilename)) throw new ArgumentNullException("zipfilename");
            if (filestozip.Count == 0) throw new ArgumentNullException("指定的文件序列为空");
            foreach (string file in filestozip)
            {
                if (!File.Exists(file)) throw new ArgumentNullException("在指定的" + file + "找不到对应的文件");
            }
            //建立事件参数
            ZipProcessEventArgs e = new ZipProcessEventArgs();
            e.FileTotal = (double)filestozip.Count;
            e.CurrentCount = 0.0;
            //建立ZIP文件流
            CreateZipFile(zipfilename, this.ZipLevel, password);

            foreach (string filename in filestozip)
            {
                ZipSingleFile(filename);
                e.CurrentCount++;
                e.CurrentFileName = filename;
                if (OnZipProcess != null)
                {
                    //触发事件
                    this.OnZipProcess(e);
                }
            }
            CloseZipFile();
        }
        /// <summary>
        /// 压缩指定的文件(组)
        /// </summary>
        /// <param name="filestozip">要压缩的文件组</param>
        /// <param name="zipfilename">压缩后的文件名</param>
        public void ZipFiles(List<string> filestozip, string zipfilename)
        {
            ZipFiles(filestozip, zipfilename, String.Empty);
        }
        /// <summary>
        /// 压缩指定的文件(组)
        /// </summary>
        public void ZipFiles()
        {
            ZipFiles(this.FilesTozip, this._zipfilename);
        }
        #endregion

        #region 解压文件
        /// <summary>
        /// 解压缩文件
        /// </summary>
        /// <param name="zipfile">压缩文件位置</param>
        /// <param name="unzipfolder">要解压缩到的文件夹</param>
        public void UnzipFile(string zipfile, string unzipfolder)
        {
            if (!File.Exists(zipfile)) throw new FileNotFoundException("指定的压缩文件不存在");
            if (String.IsNullOrEmpty(unzipfolder)) throw new ArgumentNullException("解压缩文件夹不能为空");
            if (unzipfolder != String.Empty) Directory.CreateDirectory(unzipfolder);

            ZipProcessEventArgs e = new ZipProcessEventArgs();
            ZipFile zf = new ZipFile(zipfile);
            e.FileTotal = (double)zf.Count;
            e.CurrentCount = 0.0;

            _zipInputStream = new ZipInputStream(File.OpenRead(zipfile));
            ZipEntry zipEntry;
            int size = 2048;
            byte[] data = new byte[2048];
            string unzipfilename = String.Empty;

            while ((zipEntry = _zipInputStream.GetNextEntry()) != null)
            {
                if (zipEntry.Name != String.Empty)
                {
                    if (zipEntry.CompressedSize == 0)
                        break;
                    Directory.CreateDirectory(unzipfolder + Path.GetDirectoryName(zipEntry.Name).Replace(Path.GetPathRoot(zipEntry.Name), @"\"));
                    //建立写文件流
                    unzipfilename = unzipfolder + Path.GetDirectoryName(zipEntry.Name).Replace(Path.GetPathRoot(zipEntry.Name), @"\")
                        + @"\" + Path.GetFileName(zipEntry.Name);
                    FileStream fsWriter = File.Create(unzipfilename);

                    while (true)
                    {
                        size = _zipInputStream.Read(data, 0, data.Length);
                        if (size > 0)
                            fsWriter.Write(data, 0, size);
                        else
                            break;
                    }
                    fsWriter.Flush();
                    fsWriter.Close();
                    
                    e.CurrentCount++;
                    e.CurrentFileName = unzipfilename;
                    if (OnUnzipProcess != null)
                        OnUnzipProcess(e);
                }
            }
            _zipInputStream.Close();
        }

        /// <summary>
        /// 解压缩文件
        /// </summary>
        /// <param name="zipfile">压缩文件位置</param>
        public void UnzipFile(string zipfile)
        {
            UnzipFile(zipfile, this.UnzipFolder);
        }
        #endregion

        #region 私有方法
        /// <summary>
        /// 得到文件夹下的所有文件
        /// </summary>
        /// <param name="directoryPath">目录</param>
        /// <param name="fileList">得到的文件列表</param>
        private void GetAllFile(string directoryPath, ref List<string> fileList)
        {
            string[] fileinDirectory = Directory.GetFiles(directoryPath);
            foreach (string file in fileinDirectory)
                fileList.Add(file);

            string[] subDirectory = Directory.GetDirectories(directoryPath);
            foreach (string dir in subDirectory)
                GetAllFile(dir, ref fileList);
        }
        /// <summary>
        /// 建立一个ZIP文件
        /// </summary>
        /// <param name="zipFileName">ZIP文件名</param>
        /// <param name="zipLevel">压缩文件级别</param>
        private void CreateZipFile(string zipFileName,int zipLevel, string password)
        {
            _crc = new Crc32();
            _zipStream = new ZipOutputStream(File.Create(zipFileName));
            if (!String.IsNullOrEmpty(password))
                _zipStream.Password = password;
            _zipStream.SetLevel(zipLevel);
        }
        /// <summary>
        /// 关闭ZIP文件流
        /// </summary>
        private void CloseZipFile()
        {
            _zipStream.Finish();
            _zipStream.Close();
            _zipStream = null;
        }

        /// <summary>
        /// 压缩单个文件
        /// </summary>
        /// <param name="filepath">文件名</param>
        private void ZipSingleFile(string filepath)
        {
            FileStream fs = File.OpenRead(filepath);
            byte[] buffer = new byte[fs.Length];
            fs.Read(buffer, 0, (int)fs.Length);

            ZipEntry zipEntry = new ZipEntry(filepath);
            zipEntry.DateTime = DateTime.Now;
            zipEntry.Size = fs.Length;
            fs.Close();
            fs = null;

            //设置压缩文件的CRC
            _crc.Reset();
            _crc.Update(buffer);
            zipEntry.Crc = _crc.Value;

            //将压缩文件流添加到输出文件流中
            _zipStream.PutNextEntry(zipEntry);
            _zipStream.Write(buffer, 0, buffer.Length);
        }
        #endregion

        #region IDisposable Members

        private void disposing(bool disposing)
        {
            if (disposing)
            {
                if (_zipStream != null)
                {
                    _zipStream.Close();
                    _zipStream = null;
                }
                if (_zipInputStream != null)
                {
                    _zipInputStream.Close();
                    _zipInputStream = null;
                }
                GC.SuppressFinalize(this);
            }
        }

        /// <summary>
        /// 释放占用的资源
        /// </summary>
        public void Dispose()
        {
            disposing(true);
        }

        #endregion
    }

    /// <summary>
    /// 压缩缩时的时间参数
    /// </summary>
    public class ZipProcessEventArgs : EventArgs
    {
        private string _currentfilename;
        /// <summary>
        /// 当前文件名
        /// </summary>
        public string CurrentFileName
        {
            get { return _currentfilename; }
            set { _currentfilename = value; }
        }
        private double _filetotal;
        /// <summary>
        /// 文件总数
        /// </summary>
        public double FileTotal
        {
            get { return _filetotal; }
            set { _filetotal = value; }
        }
        private double _currentcount;
        /// <summary>
        /// 当前进度
        /// </summary>
        public double CurrentCount
        {
            get { return _currentcount; }
            set { _currentcount = value; }
        }
        /// <summary>
        /// 执行百分比
        /// </summary>
        public double Percent
        {
            get 
            {
                if (this._filetotal == 0.0) return 0.0;
                return Math.Round(this._currentcount / this._filetotal,2) * 100; 
            }
        }

        public override string ToString()
        {
            return String.Format("正在处理文件{3} 进度:{0}/{1} 百分比:{2}%", this._currentcount, this._filetotal, this.Percent, this._currentfilename); 
        }
    }

}
